Explore os Tipos de Literais de Template do TypeScript e construa um motor de validação em tempo de execução para verificação robusta de strings e segurança de tipos.
Motor de Validação de Literais de Template do TypeScript: Verificação de Strings em Tempo de Execução
Os tipos de literais de template do TypeScript oferecem poderosa manipulação de strings e segurança de tipos em tempo de compilação. No entanto, essas verificações são limitadas ao tempo de compilação. Este post explora como construir um motor de validação em tempo de execução para tipos de literais de template do TypeScript, permitindo verificação robusta de strings e prevenindo erros potenciais durante a execução do programa.
Introdução aos Tipos de Literais de Template do TypeScript
Os tipos de literais de template permitem definir formas específicas de strings com base em valores literais, uniões e inferência de tipos. Isso possibilita verificação de tipos precisa e auto-completar, especialmente útil ao lidar com dados estruturados ou linguagens específicas de domínio.
Por exemplo, considere um tipo para representar códigos de moeda:
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
const validCurrency: FormattedCurrencyString = "USD-100"; // OK
const invalidCurrency: FormattedCurrencyString = "CAD-50"; // Erro de tipo em tempo de compilação
Este exemplo demonstra como o TypeScript impõe o tipo FormattedCurrencyString em tempo de compilação. No entanto, se o código da moeda vier de uma fonte externa (por exemplo, entrada do usuário, resposta de API), você precisa de validação em tempo de execução para garantir a segurança de tipos.
A Necessidade de Validação em Tempo de Execução
Embora o TypeScript forneça excelente verificação de tipos em tempo de compilação, ele não pode garantir a validade dos dados recebidos de fontes externas em tempo de execução. Confiar apenas em tipos de tempo de compilação pode levar a erros e vulnerabilidades inesperadas.
Considere o seguinte cenário:
function processCurrency(currencyString: FormattedCurrencyString) {
// ... alguma lógica que assume que a string está formatada corretamente
}
const userInput = "CAD-50"; // Suponha que isto venha da entrada do usuário
// Isto irá compilar, mas causará um erro em tempo de execução se a lógica dentro
// de `processCurrency` depender do formato.
processCurrency(userInput as FormattedCurrencyString);
Neste caso, estamos fazendo um cast de userInput para FormattedCurrencyString, contornando as verificações de tempo de compilação do TypeScript. Se processCurrency depender da string estar formatada corretamente, ela encontrará um erro em tempo de execução.
A validação em tempo de execução preenche essa lacuna, verificando se os dados recebidos em tempo de execução estão em conformidade com os tipos esperados do TypeScript.
Construindo um Motor de Validação de Literais de Template
Podemos construir um motor de validação em tempo de execução usando expressões regulares e o sistema de tipos do TypeScript. O motor receberá um tipo de literal de template e uma string como entrada e retornará se a string corresponde ao tipo.
Passo 1: Definindo um Tipo para Validação em Tempo de Execução
Primeiro, precisamos de um tipo genérico que possa representar o equivalente em tempo de execução de um tipo de literal de template. Este tipo deve ser capaz de lidar com diferentes tipos de literais de template, incluindo literais, uniões e parâmetros de tipo.
type TemplateLiteralToRegex =
T extends `${infer Start}${infer Middle}${infer End}`
? Start extends string
? Middle extends string
? End extends string
? TemplateLiteralToRegexStart & TemplateLiteralToRegexMiddle & TemplateLiteralToRegex
: never
: never
: never
: TemplateLiteralToRegexStart;
type TemplateLiteralToRegexStart = T extends `${infer Literal}` ? Literal : string;
type TemplateLiteralToRegexMiddle = T extends `${infer Literal}` ? Literal : string;
Esta definição de tipo recursiva divide o literal de template em suas partes constituintes e converte cada parte em um padrão de expressão regular.
Passo 2: Implementando a Função de Validação
Em seguida, implementamos a função de validação que recebe o tipo de literal de template e a string a ser validada como entrada. Esta função usa a expressão regular gerada por TemplateLiteralToRegex para testar a string.
function isValid(str: string, templateType: T): boolean {
const regexPattern = `^${convertTemplateLiteralToRegex(templateType)}$`;
const regex = new RegExp(regexPattern);
return regex.test(str);
}
function convertTemplateLiteralToRegex(templateType: T): string {
// Conversão básica para strings literais - expanda isto para cenários mais complexos
return templateType.replace(/[.*+?^${}()|\[\]]/g, '\\$&'); // Escapa caracteres especiais de regex
}
Esta função escapa caracteres especiais de expressão regular e cria uma expressão regular a partir do tipo de literal de template, em seguida, testa a string contra essa expressão regular.
Passo 3: Usando o Motor de Validação
Agora, você pode usar a função isValid para validar strings contra seus tipos de literais de template em tempo de execução.
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
const userInput1 = "USD-100";
const userInput2 = "CAD-50";
console.log(`'${userInput1}' is valid: ${isValid(userInput1, "USD-100" )}`); // true
console.log(`'${userInput2}' is valid: ${isValid(userInput2, "USD-100")}`); // false
console.log(`'${userInput1}' is valid: ${isValid(userInput1, `USD-${100}`)}`); // true
console.log(`'${userInput2}' is valid: ${isValid(userInput2, `USD-${100}`)}`); // false
Este exemplo demonstra como usar a função isValid para validar a entrada do usuário contra o tipo FormattedCurrencyString. A saída mostrará se as strings de entrada são consideradas válidas ou não, com base no literal de template especificado.
Cenários Avançados de Validação
O motor de validação básico pode ser estendido para lidar com cenários mais complexos, como uniões, tipos condicionais e tipos recursivos.
Lidando com Uniões
Para lidar com uniões, você pode modificar o tipo TemplateLiteralToRegex para gerar uma expressão regular que corresponda a qualquer um dos membros da união.
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
function isValidCurrencyCode(str: string, templateType: T): boolean {
const currencyCodes: CurrencyCode[] = ["USD", "EUR", "GBP"];
return currencyCodes.includes(str as CurrencyCode);
}
function isValidUnionFormattedCurrencyString(str: string): boolean {
const parts = str.split('-');
if(parts.length !== 2) return false;
const [currencyCode, amount] = parts;
if (!isValidCurrencyCode(currencyCode, currencyCode)) return false;
if (isNaN(Number(amount))) return false;
return true;
}
console.log(`'USD-100' is valid formatted string: ${isValidUnionFormattedCurrencyString('USD-100')}`);
console.log(`'CAD-50' is valid formatted string: ${isValidUnionFormattedCurrencyString('CAD-50')}`);
Lidando com Tipos Condicionais
Tipos condicionais podem ser tratados avaliando a condição em tempo de execução e gerando diferentes expressões regulares com base no resultado.
type IsString = T extends string ? true : false;
// Este exemplo requer lógica mais avançada e não é totalmente implementável usando regex simples.
// Guardas de tipo em tempo de execução oferecem uma solução mais robusta neste cenário específico.
// O código abaixo é ilustrativo e precisaria de adaptação para lidar com tipos condicionais complexos.
function isString(value: any): value is string {
return typeof value === 'string';
}
function isValidConditionalType(value: any): boolean {
return isString(value);
}
console.log(`'hello' is a string: ${isValidConditionalType('hello')}`);
console.log(`123 is a string: ${isValidConditionalType(123)}`);
Lidando com Tipos Recursivos
Tipos recursivos podem ser tratados definindo uma função recursiva que gera o padrão de expressão regular. No entanto, tome cuidado para evitar recursão infinita e erros de estouro de pilha. Para recursão profunda, abordagens iterativas com limites apropriados são cruciais.
Alternativas às Expressões Regulares
Embora as expressões regulares sejam uma ferramenta poderosa para validação de strings, elas podem ser complexas e difíceis de manter. Outras abordagens para validação em tempo de execução incluem:
- Funções de Validação Personalizadas: Escreva funções personalizadas para validar tipos específicos com base nos requisitos do seu aplicativo.
- Type Guards (Guardas de Tipo): Use guardas de tipo para refinar o tipo de uma variável em tempo de execução.
- Bibliotecas de Validação: Utilize bibliotecas de validação existentes como Zod ou Yup para simplificar o processo de validação.
Zod, por exemplo, fornece uma declaração baseada em schema que se traduz em um runtime de validação:
import { z } from 'zod';
const CurrencyCodeSchema = z.enum(['USD', 'EUR', 'GBP']);
const FormattedCurrencyStringSchema = z.string().regex(new RegExp(`^${CurrencyCodeSchema.enum.USD}|${CurrencyCodeSchema.enum.EUR}|${CurrencyCodeSchema.enum.GBP}-[0-9]+$`));
try {
const validCurrency = FormattedCurrencyStringSchema.parse("USD-100");
console.log("Valid Currency:", validCurrency);
} catch (error) {
console.error("Invalid Currency:", error);
}
try {
const invalidCurrency = FormattedCurrencyStringSchema.parse("CAD-50");
console.log("Valid Currency:", invalidCurrency); // Isso não será executado se o parse falhar.
} catch (error) {
console.error("Invalid Currency:", error);
}
Melhores Práticas para Validação em Tempo de Execução
Ao implementar validação em tempo de execução, tenha em mente as seguintes melhores práticas:
- Valide na Fronteira: Valide os dados assim que eles entram no seu sistema (por exemplo, entrada do usuário, respostas de API).
- Forneça Mensagens de Erro Claras: Gere mensagens de erro informativas para ajudar os usuários a entender por que sua entrada é inválida.
- Use uma Estratégia de Validação Consistente: Adote uma estratégia de validação consistente em todo o seu aplicativo para garantir a integridade dos dados.
- Teste sua Lógica de Validação: Teste minuciosamente sua lógica de validação para garantir que ela identifique corretamente dados válidos e inválidos.
- Equilibre Performance e Segurança: Otimize sua lógica de validação para performance, garantindo ao mesmo tempo que ela previne efetivamente vulnerabilidades de segurança. Evite regex excessivamente complexas que levem a negação de serviço.
Considerações de Internacionalização
Ao lidar com validação de strings em um contexto global, você precisa considerar internacionalização (i18n) e localização (l10n). Diferentes localidades podem ter regras diferentes para formatar strings, como datas, números e valores de moeda.
Por exemplo, o símbolo da moeda do Euro (€) pode aparecer antes ou depois do valor, dependendo da localidade. Da mesma forma, o separador decimal pode ser um ponto (.) ou uma vírgula (,).
Para lidar com essas variações, você pode usar bibliotecas de internacionalização como Intl, que fornece APIs para formatar e analisar dados sensíveis à localidade. Por exemplo, você poderia adaptar o exemplo anterior para lidar com diferentes formatos de moeda:
function isValidCurrencyString(currencyString: string, locale: string): boolean {
try {
const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: currencyString.substring(0,3) }); // Exemplo muito básico
// Tenta analisar a moeda usando o formatador. Este exemplo é intencionalmente muito simples.
return true;
} catch (error) {
return false;
}
}
console.log(`USD-100 is valid for en-US: ${isValidCurrencyString('USD-100', 'en-US')}`);
console.log(`EUR-100 is valid for fr-FR: ${isValidCurrencyString('EUR-100', 'fr-FR')}`);
Este trecho de código fornece um exemplo fundamental. A internacionalização adequada requer um manuseio mais completo, possivelmente utilizando bibliotecas ou APIs externas especificamente projetadas para formatação e validação de moeda em diferentes localidades.
Conclusão
A validação em tempo de execução é uma parte essencial da construção de aplicações TypeScript robustas e confiáveis. Ao combinar os tipos de literais de template do TypeScript com expressões regulares ou métodos de validação alternativos, você pode criar um motor poderoso para verificar a validade de strings em tempo de execução.
Essa abordagem aprimora a segurança de tipos, previne erros inesperados e melhora a qualidade geral do seu código. À medida que você constrói aplicações mais complexas, considere incorporar validação em tempo de execução para garantir que seus dados estejam em conformidade com os tipos e formatos esperados.
Exploração Adicional
- Explore técnicas avançadas de expressões regulares para cenários de validação mais complexos.
- Investigue bibliotecas de validação como Zod e Yup para validação baseada em schema.
- Considere usar técnicas de geração de código para gerar automaticamente funções de validação a partir de tipos TypeScript.
- Estude bibliotecas e APIs de internacionalização para lidar com dados sensíveis à localidade.